#include "thread.h"
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>

#include "xmalloc.h"
#include "sighandler.h"

static uint8_t thread_count = MAX_THREADS;
static uint8_t thread_idx = 0;
__NCTLS int thrd_num = -1;
static threadpool_t *thrds = NULL;
static pthread_mutex_t thrd_create = PTHREAD_MUTEX_INITIALIZER;
#ifdef MAINTHRD_HEARTBEAT
static pthread_mutex_t thrd_wait = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t main_thrd_wait = PTHREAD_COND_INITIALIZER;
#endif

typedef struct pthread_init {
  start_routine_t task;
  void *arg;
} pthread_init_t;


static void
set_pthread_count(int max_threads)
{
  if (max_threads <= MAX_THREADS) {
    thread_count = max_threads;
  }
}

int
get_pthread_count(void)
{
  return (thread_count);
}

int
get_thread_num(void)
{
  return (thrd_num);
}

threadpool_t *
get_thread_data(const uint8_t tidx)
{
  if (tidx > thread_count) return (NULL);
  return (&(thrds[tidx]));
}

threadpool_t *
get_this_thread_data(void)
{
  return (get_thread_data(thrd_num));
}

void
init_threads(const uint8_t max_threads)
{
  set_pthread_count(max_threads);
  thrds = (threadpool_t *) ncmalloc(thread_count, sizeof(threadpool_t));
}

void
free_threads(void)
{
  ncfree(thrds, thread_count*sizeof(threadpool_t));
}

#ifdef MAINTHRD_HEARTBEAT
static void
mainthrd_heartbeat(void)
{
  struct timespec main_thrd_time2wait;
  struct timeval now;

  memset(&main_thrd_time2wait, 0, sizeof(struct timespec));
  gettimeofday(&now, NULL);
  main_thrd_time2wait.tv_sec = now.tv_sec + MAINTHRD_HEARTBEAT;

  while (pthread_cond_timedwait(&main_thrd_wait, &thrd_wait, &main_thrd_time2wait) == ETIMEDOUT) {
    ncmem_usage();
    gettimeofday(&now, NULL);
    main_thrd_time2wait.tv_sec = now.tv_sec + MAINTHRD_HEARTBEAT;
  }
}
static int
mainthrd_heartbeat_exit(void)
{
  return (pthread_cond_signal(&main_thrd_wait));
}
#endif


static void *
pthread_init_routine(void *arg)
{
  void *ret;
  pthread_init_t *data = (pthread_init_t *) arg;

  pthread_setsigs();
  thrd_num = thread_idx;
  thread_idx++;
  I("Thread(%d): init ..", thrd_num);
  pthread_mutex_unlock(&thrd_create);
  ret = data->task(data->arg);
  ncfree(data, sizeof(pthread_init_t));
  return (ret);
}

int
create_thread(start_routine_t cb, void *arg)
{
  threadpool_t *tp;
  pthread_init_t *data;

  pthread_mutex_lock(&thrd_create);
  D("creating Thread(%d) ..", thread_idx);
  data = (pthread_init_t *) ncmalloc(1, sizeof(pthread_init_t));
  data->task = cb;
  data->arg = arg;
  tp = (threadpool_t *) get_thread_data(thread_idx);
  return (pthread_create(&(tp->thrd), tp->attr, pthread_init_routine, (void *)data) != 0);
}

int
threads_wait(int data_size)
{
  threadpool_t *tp;

#ifdef MAINTHRD_HEARTBEAT
  I2("mainthread heartbeat loop");
  mainthrd_heartbeat();
#endif
  for (int idx = 0; idx < thread_count; idx++) {
    pthread_join(thrds[idx].thrd, NULL);
    pthread_mutex_lock(&thrd_create);
    tp = (threadpool_t *) get_thread_data(idx);
    if (data_size > 0) {
      ncfree(tp->data, data_size);
    }
    pthread_mutex_unlock(&thrd_create);
  }
  return (0);
}

void
threads_mtx_lock(void)
{
#ifdef MAINTHRD_HEARTBEAT
  mainthrd_heartbeat_exit();
#endif
  pthread_mutex_lock(&thrd_create);
}

void
threads_mtx_unlock(void)
{
  pthread_mutex_unlock(&thrd_create);
}
